package org.ellwood.core.utils;

import org.apache.commons.lang3.StringUtils;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.*;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

/**
 * @ProjectName: ellwood-boot-pro
 * @PackageName: org.ellwood.core.utils
 * @ClassName: SpringApplicationContextUtils
 * @Description: // spring工具类 方便在（非spring ioc）非spring管理环境中获取bean
 *                  SpringBootApplicationContextUtil 通过ApplicationContext上下文对象自定义获取bean
 * @Author: Ellwood
 * @CreateDate: 2024/6/12 - 下午3:44
 * @Version: V1.0.0
 */
@Component
public class EllSpringAppContextUtils implements BeanFactoryPostProcessor, ApplicationContextAware {

    /**
     * spring应用上下文环境
     */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    private final static String[] localEnv={"local", "dev"};

    /**
     * 获取对象
     * @param name bean名称
     * @return 返回对象结果
     * @param <T> 对象实体
     * @throws BeansException 获取对象异常
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name)throws BeansException{
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型胃requiredType的对象
     * @param clazz 类型
     * @return 返回对象结果
     * @param <T> 对象实体
     * @throws BeansException 获取对象异常
     */
    public static <T> T getBean(Class<T> clazz)throws BeansException{
        return (T) beanFactory.getBean(clazz);
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义，则返回true
     * @param name bean名称
     * @return 返回结果
     */
    public static boolean containsBean(String name){
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。
     *                          如果与给定名字相应的bean定义没有被找到，将会抛出一个异常（NoSuchBeanDefinitionException）
     * @param name bean名称
     * @return 返回结果
     * @throws NoSuchBeanDefinitionException 异常
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
        return beanFactory.isSingleton(name);
    }

    /**
     *
     * @param name bean名称
     * @return Class 注册对象的类型
     * @throws NoSuchBeanDefinitionException 异常
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException{
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名，则返回这些别名
     * @param name bean名称
     * @return 返回结果
     * @throws NoSuchBeanDefinitionException 异常
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException{
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     * @param invoker 被代理对象
     * @return 返回结果
     * @param <T> 对象实体
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker){
        return (T) AopContext.currentProxy();
    }

    /**
     * 获取当前的环境配置，无配置返回null
     * @return 返回结果
     */
    public static String[] getActiveProfiles(){
        return applicationContext.getEnvironment().getActiveProfiles();
    }

    /**
     * 注册bean
     * @param beanName 注册bean名称
     * @param clazz 类型
     * @param function bean定义
     * @return 注册bean实例
     * @param <T> 对象实体
     */
    public static <T> T registerBean(String beanName, Class<T> clazz,
                                     Function<BeanDefinitionBuilder, AbstractBeanDefinition> function){
        // 生成bean定义
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(clazz);
        // 执行bean处理函数
        AbstractBeanDefinition beanDefinition = function.apply(beanDefinitionBuilder);
        BeanDefinitionRegistry beanFactory= (BeanDefinitionRegistry) EllSpringAppContextUtils.beanFactory;
        // 判断是否通过beanName注册
        if (StringUtils.isNotBlank(beanName)&&!containsBean(beanName)) {
            beanFactory.registerBeanDefinition(beanName,beanDefinition);
            return getBean(beanName);
        }else{
            // 非命名bean注册
            String name = BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, beanFactory);
            return getBean(name);
        }

    }

    /**
     * 注册bean
     * @param beanName 注册的bean名称
     * @param clazz 类型
     * @param args 构造参数
     * @param property bean属性集
     * @return 注册的bean实例
     * @param <T>  返回实体类型
     */
    public static <T> T registerBean(String beanName, Class<T> clazz, List<Objects> args, Map<String,Object> property){
        return registerBean(beanName,clazz,beanDefinitionBuilder -> {
            // 放入构造参数
            if (!EllCollectorUtils.isEmpty(args)) {
                args.forEach(beanDefinitionBuilder::addConstructorArgValue);
            }
            // 放入属性
            if (!EllCollectorUtils.isEmpty(property)) {
                property.forEach(beanDefinitionBuilder::addPropertyValue);
            }
            return beanDefinitionBuilder.getBeanDefinition();
        });
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        EllSpringAppContextUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        EllSpringAppContextUtils.applicationContext=applicationContext;
    }

    public static String getActive(){
        return applicationContext.getEnvironment().getActiveProfiles()[0];
    }

    /**
     * 判断是否是本地环境
     * @return 返回结果
     */
    public static boolean isLocalEnv(){
        List<String> localEnvArr = Arrays.asList(localEnv);
        return localEnvArr.contains(getActive());
    }
}
